dpi awareなimgを表示する 〜完結編〜
ブラウザで高解像度スクリーンショットを適切な論理サイズで表示する
https://gyazo.com/e673044c42b7fd8fd85a4f87687446c8
こんにちは、daiiz /daiiz/daiiz.icon です
NotaでScrapboxを作っています
Nota
https://gyazo.com/334156e5ddaec301eec92d6a8f2bf980
Scrapbox
https://gyazo.com/ed58dad9ca677c8ab54867fe08cf6817
Wiki、複数人で同時編集できるノート
リンク資産
時間を超えたのページの有効活用
そのままプレゼンモードにもなる
Pixel Slate
https://gyazo.com/1ba014cd7a772fa73c5ff9dd136c9fab https://store.google.com/gb/product/pixel_slate
最高 daiiz.icon*5
Gyazo uploader for Pixel Slate
https://youtu.be/KtDG-SfLn9I
Chrome OS標準スクショ機能によって保存された画像ファイルを都度Gyazoにuploadする
はじめてRustを書いた
これはこれでいつか話したい
ブラウザで高解像度スクリーンショットを適切な論理サイズで表示する
解決したいこと
Retinaディスプレイで撮影した画像をimgタグで表示した時、縦横の大きさがそれぞれ2倍になる
<img src="screenshot.png">
https://gyazo.com/8e4556a979e216b5d9427ae0e6564bb8
前回までのあらすじ
ここにDPI情報 (単位メートルあたりのピクセル数) が書かれている
natural sizeを計算してCSS width, heightを与える
https://gyazo.com/490521889cc22cc419802f08a983bcaf/raw
ブラウザで一連の処理を行いDPI awareで表示するimg Custom Element
pHYsを読んでみて
うまくいけばnatural sizeをCSSで指定して表示
失敗したらimg要素にfallback
→ clientで画像のバイナリを読む力技
確かに動くが、これしか手はないものか?
dpi awareなimg要素は将来的にも登場しない?
devicePixelRatio > 1な環境で撮られるスクショ画像は増え続けるはず
ほかのアイデアも含めて、自前でサイズ決定して表示するのはだいぶ複雑
このあたりの議論どうなっているのだろう
課題
PNG以外のフォーマットどうするか問題
デバッグ大変
適切なCORS設定が必要
PNG以外のフォーマットどうするか
同様にバイナリ読めばいいのだけなのだが、大変
PNGで配信するのやめてWebPにしよう、となったら?
デバッグ大変
DPI awareで表示されないとき
pHYs読む過程でのbugなのか
そもそもDPI情報を持っていないのか
ぱっと見でわからない
便利な副生成物ができた
CORS設定
gyazo.com から配信される画像を scrapbox.io で読む例
Gyazoからのresponse header
Access-Control-Allow-Origin: scrapbox.io
Scrapboxで、画像のバイナリを読むために必要
https://gyazo.com/ebe013b773b69a37b775604b134d0c45
先にgyazo.comで画像を見た際にキャッシュされて、scrapboxでも使い回された
一応 fetch(url, { cache: no-store })とすればこのエラーは起きない
が、cacheの活用が一切できなくなってしまう
さらに考えた
サーバーサイドでpHYsを読む?
width=80pxのRetina画像をuploadすると、<img width="40" ...>というimgタグが生成されて埋め込まれる
https://gyazo.com/71c2a7b98f39f625f0531f9cee1ef35d
読んだ結果をCustom HTTP Headerに載せて返す?
X-Image-Width: 1000
X-Image-DPI: 144
しかし、結局CORS縛りからは逃れられない
Chromiumを読んでみる
https://gyazo.com/ab3d56876cabf6aa617437a1eb954883
もしかしたらpHYsを読んでいる箇所があるかも
もしくは悪手である旨コメントがあるかも
そもそもnaturalWidth, naturalHeightをどうやって算出しているのか
ビューワが参照している情報を調べる
ここになければ解決手法はないのでは
HTMLImageElementが実装すべき関数が定義されているヘッダファイル
関係ありそうな関数の目星を付けて読んでいく
今日はダイジェスト版
導出過程1
LayoutSize定数を返す関数
code:cc
LayoutSize HTMLImageElement::DensityCorrectedIntrinsicDimensions() const {
IntSize overridden_intrinsic_size = GetOverriddenIntrinsicSize();
if (!overridden_intrinsic_size.IsEmpty())
return LayoutSize(overridden_intrinsic_size);
ImageResourceContent* image_resource = GetImageLoader().GetContent();
if (!image_resource || !image_resource->HasImage())
return LayoutSize();
float pixel_density = image_device_pixel_ratio_;
このあたりが大事
code:cc
if (image_resource->HasDevicePixelRatioHeaderValue() &&
image_resource->DevicePixelRatioHeaderValue() > 0)
pixel_density = 1 / image_resource->DevicePixelRatioHeaderValue();
あとはLayoutSizeを取得して、px_density補正しているだけ
code:cc
RespectImageOrientationEnum respect_image_orientation =
LayoutObject::ShouldRespectImageOrientation(GetLayoutObject());
LayoutSize natural_size(
image_resource->IntrinsicSize(respect_image_orientation));
natural_size.Scale(pixel_density);
return natural_size;
}
最終的にサイズを決定しているのはnatural_size
natural_size.Scale(pixel_density)することで論理サイズを決定してる
導出過程2
DevicePixelRatioHeaderValueを読み解く
image_resource->DevicePixelRatioHeaderValue() の実装
code:cc
float ImageResourceContent::DevicePixelRatioHeaderValue() const {
return device_pixel_ratio_header_value_;
}
すでにどこかでdevice_pixel_ratio_header_value_は確定しているらしい
導出過程3
device_pixel_ratio_header_value_をセットするところ
HeaderとはHTTP Response Headerのこと
http_names::kContentDPRというフィールドを読んでいる
code:cc
scoped_refptr<Image> ImageResourceContent::CreateImage(bool is_multipart) {
String content_dpr_value =
info_->GetResponse().HttpHeaderField(http_names::kContentDPR);
この後 content_dpr_valueを加工してdevice_pixel_ratio_header_value_を確定している
導出過程4
http_names::kContentDPRの定義
code:cc
const AtomicString& kContentDPR = reinterpret_cast<AtomicString*>(&names_storage)15; 導出過程5
HTTP HeaderがNameEntryとして列挙されているところに行き着いた
code:cc
struct NameEntry {
const char* name;
unsigned hash;
unsigned char length;
};
code:cc
...
{ "Cache-Control", 7757542, 13 },
{ "Content-DPR", 8569724, 11 },
{ "Content-Disposition", 362682, 19 },
...
つまり、HTTP HeaderにContent-DPRを付けて画像を配信すればよい!!
$ DPR = \frac{DPI}{72}という関係が成り立つ
window.devicePixelRatioで得られる
具体例
table:DPRの例
MacBook Pro 2017 Retina 2.0
諸々実験しているときは HTTP Client Hints に属していた仕様
clientはこの値を考慮して各辺のCSSピクセル数を決定する
書き方
https://gyazo.com/f9481bc6bde770c205d7deea67275367 https://httpwg.org/http-extensions/client-hints.html#content-dpr
Retina画像ならContent-DPR: 2.0と書く
Akamaiによる解説
対応ブラウザ
現時点ではChrome, Operaのみ。Blink以外に実装がない。
https://gyazo.com/185a6e6c63df9a33231bfa218a85fbb9 https://caniuse.com/#search=Content-DPR
Gyazoで対応!
https://i.gyazo.com/7127a0c2a987ea50dbba0ebd6455c206.png
https://gyazo.com/7127a0c2a987ea50dbba0ebd6455c206/raw
Scrapboxでも
[https://gyazo.com/282d9be5cbc1f2d9a3c1f1b0eb5413d2/raw]
https://gyazo.com/282d9be5cbc1f2d9a3c1f1b0eb5413d2/raw
まとめ
高解像度画像を、普通のimg要素で、DPR awareで表示できる
平成のうちに解決まで漕ぎ着けてよかった
/icons/hr.icon
Q&A
Chrome Platform Statusによると、広くサポートの意向はありそう
https://svgscreenshot.appspot.com/o/ac293a455e495166ced6b9bf3ae8f3ed.svg https://www.chromestatus.com/feature/5504430086553600
Safari (WebKit)
停滞気味
Firefox
はい。この場合は/thumb/1000を参照し、適当なサイズにresizeされたサムネイルが配信されるのですが、これにはContent-DPR headerは載せていないです。
/thumb/1000をDPI awareで、naturalWidth=1000pxと解釈した時、(1000 * DPR)pxの画像を配信することになるが、直感的に理解しづらいため。
/dpr-aware-thumb/1000など、別のendpointを用意するとよいかもしれない。
/icons/hr.icon
追記